home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Pascal / Snippets / PNL Libraries / NeoTextBox.p < prev    next >
Text File  |  1997-03-25  |  12KB  |  327 lines

  1. unit NeoTextBox;
  2.  
  3. { Written in C by Bryan K. Ressler (Beaker), from Develop issue 9 }
  4.  
  5. interface
  6.  
  7.     uses
  8.         Types;
  9.  
  10.     const
  11.         ntbJustFull = 128;
  12.  
  13.     procedure NeoTextBox (theText: Ptr; textLen: longint; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
  14.  
  15. implementation
  16.  
  17.     uses
  18.         Script, FixMath, Fonts, TextEdit, TextUtils, MyLowLevel, MyMathUtils;
  19.  
  20.     const
  21.         kReturnChar = 13;
  22.  
  23. {}
  24. {* NTBLineHeight - figures line height}
  25. {*}
  26. {* Input:    theText        the entire text that was given to the NeoTextBox call}
  27. {*            textLen        the length in bytes of the text}
  28. {*            lhCode        the line height code that was passed to NeoTextBox}
  29. {*            startY        VAR - we return the starting vertical pen location here}
  30. {*}
  31. {* Output:    returns the line height to use}
  32.     function NTBLineHeight (theText: Ptr; textLen: longint; wrapBox: Rect; lhCode: integer; var startY: integer): integer;
  33.         const
  34.             kTrueTypeTrap = $54;
  35.             kUnimplTrap = $9f;
  36.  
  37.         var
  38.             asc, desc: integer;    { Used in the OutlineMetrics calls }
  39.             fInfo: FontInfo;        { The old-style font information record }
  40.             frac: Point;        { The fraction for the TrueType calls }
  41.             lineHeight: integer;    { The return value }
  42.             hasTrueType: boolean;
  43.             err: OSErr;
  44.     begin
  45.  
  46.         GetFontInfo(fInfo);
  47.         if lhCode < 0 then begin
  48.  
  49. {            If the user has specified variable-height lines, we need to try}
  50. {            to determine the tallest ascent in the given text.  We can only}
  51. {            really do this if the font is a TrueType font.  Otherwise, we}
  52. {            punt and use old-fashioned GetFontInfo numbers.}
  53.  
  54.             frac.h := 1;
  55.             frac.v := 1;
  56.             hasTrueType := NGetTrapAddress(kTrueTypeTrap, ToolTrap) <> NGetTrapAddress(kUnimplTrap, ToolTrap);
  57.             if hasTrueType & IsOutline(frac, frac) then begin
  58.  
  59. {                At this Point we know the current font is a TrueType font, so}
  60. {                we do an OutlineMetrics call with our full text.  It will put}
  61. {                the tallest character ascent into asc, and the deepest descent}
  62. {                into desc.  Then we choose between whichever's most between}
  63. {                the old-style ascent/descent and the numbers we get from}
  64. {                the OutlineMetrics call.}
  65. {            }
  66.  
  67.                 err := OutlineMetrics(textLen, theText, frac, frac, asc, desc, nil, nil, nil);
  68.  
  69.             end
  70.             else begin
  71.  
  72. {                At this Point we know the current font isn't TrueType, so we}
  73. {                just use the old way of calculating line height.}
  74.                 err := -1;
  75.             end;
  76.             if err <> noErr then begin
  77.                 asc := 0;
  78.                 desc := 0;
  79.             end;
  80.             lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
  81.             startY := wrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
  82.         end
  83.         else if lhCode = 0 then begin
  84.  
  85.         {}
  86. {            If the user has specified "default" line height, he just wants us}
  87. {            to get the line height from the FontInfo record.}
  88. {        }
  89.  
  90.             lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
  91.             startY := wrapBox.top + fInfo.ascent + fInfo.leading;
  92.  
  93.         end
  94.         else begin
  95.  
  96.         { If the user has provided a specific line height, we just trust}
  97. {            them.  We can't really generate too good of a starting vertical}
  98. {            coordinate, but we munge one together anyway.}
  99. {        }
  100.  
  101.             lineHeight := lhCode;
  102.             startY := wrapBox.top + lhCode + fInfo.leading;
  103.  
  104.         end;
  105.  
  106.         NTBLineHeight := lineHeight;
  107.     end;
  108.  
  109. {}
  110. {* NTBDraw - draws a line with appropriate justification}
  111. {*}
  112. {* Input:    breakCode    the break code that was returned from StyledLineBreak}
  113. {*            lineStart    pointer to the beginning of the text for the current line}
  114. {*            lineBytes    the length in bytes of the the text for this line}
  115. {*            wrapBox        the box within which we're wrapping}
  116. {*            align        the text alignment as specified by the user}
  117. {*            curY        our current vertical pen coordinate}
  118. {*            boxWidth    the width of wrapBox (since NeoTextBox already calculated it)}
  119. {*}
  120. {* Output:    none (draws on the screen)}
  121. {}
  122.     procedure NTBDraw (breakCode: StyledLineBreakCode; lineStart: Ptr; lineBytes: longint; wrapBox: Rect; align: integer; curY: integer; boxWidth: integer);
  123.         var
  124.             blackLen: longint;    { Length of non-white characters }
  125.             slop: integer;        { Number of pixels of slop for full just }
  126.     begin
  127.  
  128.     {}
  129. {        The first thing we do here is determine the length of the "black" part}
  130. {        of the current line.  This excludes spaces, carriage returns, and other}
  131. {        white stuff depending on the language.  How do we know what to elim-}
  132. {        inate?  We DON'T!  So we ask our friend the Script Manager to do it}
  133. {        for us.  VisibleLength returns the number of bytes that we should use}
  134. {        for pixel width calculations.}
  135. {    }
  136.  
  137.         blackLen := VisibleLength(lineStart, lineBytes);
  138.  
  139.         if align = ntbJustFull then begin
  140.  
  141.         {}
  142. {            For full justification, we need to calculate the "slop" space on}
  143. {            the line that's not filled up by the text.  Then we move to the}
  144. {            margin and get ready to draw BUT WAIT!  If this is the last line of}
  145. {            a paragraph, we need to draw it with whatever the system justifi-}
  146. {            cation is.  So if the break code indicates we're at the end of the}
  147. {            text, or we can find a carriage return there ourselves, we just}
  148. {            change the text alignment to the current default system text align-}
  149. {            ment and fall through without drawing.  If it's just another line}
  150. {            within a paragraph, we use the handy Script Manager routine DrawJust}
  151. {            to draw it justified.  In languages like Arabic, full justification}
  152. {            is performed differently than just spacing out the words.  DrawJust}
  153. {            handles cases like these correctly.}
  154. {            }
  155. {            Note that when we go looking for the carriage return at the end of}
  156. {            the line, it's okay to simply index to the last Byte of the string.}
  157. {            This is because the carriage return character, 0x0d, is in the}
  158. {            control-code range, which is defined to never be the low Byte of a}
  159. {            two Byte character.}
  160. {        }
  161.  
  162.             slop := boxWidth - TextWidth(lineStart, 0, blackLen);
  163.             MoveTo(wrapBox.left, curY);
  164.             if (breakCode = smBreakOverflow) | (AddPtrLong(lineStart, lineBytes - 1)^ = kReturnChar) then begin
  165.                 align := GetSysDirection;
  166.             end
  167.             else begin
  168.                 DrawJust(lineStart, blackLen, slop);
  169.             end;
  170.         end;
  171.  
  172.     {}
  173. {        For the rest of the text alignments (left, center, and right), we just}
  174. {        move the pen to the right place and draw the text with DrawText.  Note}
  175. {        that the alignments that could have come into the NeoTextBox call have}
  176. {        been resoved down to one of the four constants teForceLeft, teJustRight}
  177. {        teJustCenter, or ntbJustFull.}
  178. {    }
  179.  
  180.         case align of
  181.             teForceLeft, teJustLeft: 
  182.                 MoveTo(wrapBox.left, curY);
  183.             teJustRight: 
  184.                 MoveTo(wrapBox.right - TextWidth(lineStart, 0, blackLen), curY);
  185.             teJustCenter: 
  186.                 MoveTo(wrapBox.left + (boxWidth - TextWidth(lineStart, 0, blackLen)) div 2, curY);
  187.             otherwise
  188.                 ;
  189.         end;
  190.         if align <> ntbJustFull then begin
  191.             DrawText(lineStart, 0, lineBytes);
  192.         end;
  193.     end;
  194.  
  195. {}
  196. {* NeoTextBox - word-wraps text inside a given box}
  197. {*}
  198. {* Input:    theText        the text we need to wrap}
  199. {*            textLen        the length in bytes of the text}
  200. {*            wrapBox        the box within which we're wrapping}
  201. {*            align        the text alignment}
  202. {*                            teForceLeft, teFlushLeft    left justified}
  203. {*                            teJustCenter, teCenter        center justified}
  204. {*                            teJustRight, teFlushRight    right justified}
  205. {*                            ntbJustFull                    full justified}
  206. {*                            teJustLeft, teFlushDefault    system justified}
  207. {*            lhCode        the line height code that was passed to NeoTextBox}
  208. {*                            < 0        variable - based on tallest character}
  209. {*                            0        default - based on GetFontInfo}
  210. {*                            > 0        fixed - use lhCode as the line height}
  211. {*            endY        VAR - if non-nil, the vertical coord of the last line}
  212. {*            lhUsed        VAR - if non-nil, the line height used to draw the text}
  213. {*}
  214. {* Output:    returns the number of line drawn total (even if they drew outside of}
  215. {*            the boundries of wrapBox)}
  216. {}
  217.     procedure NeoTextBox (theText: Ptr; textLen: longint; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
  218.         var
  219.             oldClip: RgnHandle;        { Saved clipping region }
  220.             breakCode: StyledLineBreakCode;        { Returned code from StyledLineBreak }
  221.             fixedMax: Fixed;        { boxWidth converted to fixed Point }
  222.             wrapWid: Fixed;        { Width to wrap to }
  223.             boxWidth: integer;        { Width of the wrapBox }
  224.             lineBytes: longint;        { Number of bytes in one line }
  225.             lineHeight: integer;        { Calculated line height }
  226.             curY: integer;            { Current vertical pen location }
  227.             textLeft: longint;        { Pointer to remaining bytes of text }
  228.             lineStart: Ptr;        { Pointer to beginning of a line }
  229.             textEnd: Ptr;        { Pointer to the end of input text }
  230.     begin
  231.     {}
  232. {        First, we save the old clipping region and clip to wrapBox.  Then, figure}
  233. {        the width of wrapBox, and make a fixed Point version of it.  Also, resolve}
  234. {        the text alignment teFlushDefault to be the default system text alignment.}
  235. {    }
  236.         oldClip := NewRgn;
  237.         GetClip(oldClip);
  238.         ClipRect(wrapBox);
  239.         boxWidth := wrapBox.right - wrapBox.left;
  240.         fixedMax := Long2Fix(boxWidth);
  241.         if align = teFlushDefault then begin
  242.             align := GetSysDirection;
  243.         end;
  244.  
  245.     {}
  246. {        Now we call NTBLineHeight to calculate the appropriate line height.  It}
  247. {        also figures our starting vertical pen location in curY based on the}
  248. {        line height and other metric parameters.  Clear lineCount, set}
  249. {        lineStart to Point to the beginning of the text, calculate textEnd for}
  250. {        comparison to know when we're done, and preset textLeft to be all the}
  251. {        text.}
  252. {    }
  253.  
  254.         lineHeight := NTBLineHeight(theText, textLen, wrapBox, lhCode, curY);
  255.         lineCount := 0;
  256.         lineStart := theText;
  257.         textEnd := AddPtrLong(theText, textLen);
  258.         textLeft := textLen;
  259.  
  260.     {}
  261. {        This is the main wrap-and-draw loop.  I bet you never thought wrapping}
  262. {        text could be so easy...}
  263. {    }
  264.  
  265.         while textLeft > 0 do begin
  266.  
  267.         {}
  268. {            Every line, we have to preset lineBytes to something non-zero.}
  269. {            This tells StyledLineBreak that we're drawing the first format}
  270. {            run on the line (of course, for us, there's only ONE format run}
  271. {            total).  Also preset wrapWid.  StyledLineBreak will always modify}
  272. {            lineBytes (to tell you how many bytes are on this line), and will}
  273. {            modify wrapWid, so we have to reset them each line.}
  274. {        }
  275.  
  276.             lineBytes := 1;
  277.             wrapWid := fixedMax;
  278.  
  279.             breakCode := StyledLineBreak(lineStart, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
  280.  
  281.         {}
  282. {            Now that the Script Manager has done all the really hard work for}
  283. {            us, we draw the line.  We already knew lineStart, StyledLineBreak}
  284. {            gave us lineBytes, which we pass to NTBDraw.  It'll Handle the}
  285. {            different text alignments itself.}
  286. {        }
  287.  
  288.             NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
  289.  
  290.         {}
  291. {            Now we advance our vertical position down by the height of one}
  292. {            line, advance lineStart by the number of bytes we just drew,}
  293. {            calculate a new textLeft, and increment our line count.}
  294. {        }
  295.  
  296.             curY := curY + lineHeight;
  297.             lineStart := AddPtrLong(lineStart, lineBytes);
  298.             textLeft := textLeft - lineBytes;
  299.             lineCount := lineCount + 1;
  300.  
  301.         end;
  302.  
  303.     {}
  304. {        Well that was a job well done.  Let's return some useful values, too.}
  305. {        If the user gave pointers for endY and lhUsed, we stuff our ending}
  306. {        vertical coordinate and the line height we calculated into those,}
  307. {        respectively.  These allow the guy to put something after the wrapped}
  308. {        text (or at least know the right place to put it).}
  309. {    }
  310.  
  311.         endY := curY - lineHeight;
  312.         lhUsed := lineHeight;
  313.  
  314.     {}
  315. {        Finally, restore the clipping region, dispose of the region, and}
  316. {        return the TOTAL number of lines drawn (note that we didn't stop}
  317. {        drawing when curY advanced past wrapBox->bottom.  This way, the user}
  318. {        could tell that the text overflowed wrapBox.  If you want to know how}
  319. {        many lines fit, divide wrapBox by lhUsed.  This way, you get the best}
  320. {        of both worlds.}
  321. {    }
  322.  
  323.         SetClip(oldClip);
  324.         DisposeRgn(oldClip);
  325.     end;
  326.  
  327. end.